package minimips.executioner;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

import minimips.computer.Computer;
import minimips.exception.DataHazardException;
import minimips.exception.MemoryInaccessibleException;
import minimips.exception.StallException;
import minimips.exception.StopInstructionException;
import minimips.isa.Instruction;

public class Pipeline2 {

	private Computer computer;
	private int currentCycle;
	
	private Queue<Integer> instructionFetchQueue;
	private Queue<Integer> instructionDecodeQueue;
	private Queue<Integer> instructionExecuteQueue;
	private Queue<Integer> instructionMemQueue;
	private Queue<Integer> instructionWritebackQueue;
	
	private List<PipelineListener> listeners;
	
	public Pipeline2(Computer computer) {
		this.computer = computer;
		
		this.currentCycle = 1;
		this.instructionFetchQueue = new LinkedList<Integer>();
		this.instructionDecodeQueue = new LinkedList<Integer>();
		this.instructionExecuteQueue = new LinkedList<Integer>();
		this.instructionMemQueue = new LinkedList<Integer>();
		this.instructionWritebackQueue = new LinkedList<Integer>();
		
		this.listeners = new ArrayList<PipelineListener>();
	}
	
	public void runCycle() {
		if (this.currentCycle == 1) {
			this.instructionFetchQueue.add(Computer.FIRST_CODE_SEGMENT_ADDRESS);
		}
		
		try {
			if (!this.instructionWritebackQueue.isEmpty()) {
				Integer curMemAddress = instructionWritebackQueue.poll();
				if (curMemAddress != null) {
					Instruction retrieved = computer.getInstruction(curMemAddress);
					if (retrieved != null) {
						retrieved.writeBack(computer);
						this.fireOnWriteBack(retrieved);
					}
				}
			}
			
			if (!this.instructionMemQueue.isEmpty()) {
				Integer curMemAddress = instructionMemQueue.peek();
				try {
					if (curMemAddress != null) {
						Instruction retrieved = computer.getInstruction(curMemAddress);
						if (retrieved != null) {
							retrieved.memoryAccess(computer);
							instructionMemQueue.remove();
							instructionMemQueue.add((int) computer.getIf_id_npc().getRegisterValue());
							instructionWritebackQueue.add(curMemAddress);
							this.fireOnMemoryAccess(retrieved);
						}
					}
				} catch (MemoryInaccessibleException ex) {
					throw new StallException();
				}
			}
			
			if (!this.instructionExecuteQueue.isEmpty()) {
				Integer curMemAddress = instructionExecuteQueue.poll();
				if (curMemAddress != null) {
					Instruction retrieved = computer.getInstruction(curMemAddress);
					if (retrieved != null) {
						retrieved.execute(computer);
						instructionExecuteQueue.add((int) computer.getIf_id_npc().getRegisterValue());
						instructionMemQueue.add(curMemAddress);
						this.fireOnExecute(retrieved);
					}
				}
			}
			
			if (!this.instructionDecodeQueue.isEmpty()) {
				Integer curMemAddress = instructionDecodeQueue.peek();
				try {
					if (curMemAddress != null) {
						Instruction retrieved = computer.getInstruction(curMemAddress);
						if (retrieved != null) {
							retrieved.decode(computer);
							instructionDecodeQueue.remove();
							instructionDecodeQueue.add((int)computer.getIf_id_npc().getRegisterValue());
							instructionExecuteQueue.add(curMemAddress);
							this.fireOnDecode(retrieved);
						}
					}
				} catch (DataHazardException ex) {
					throw new StallException();
				}
			}
			
			if (!this.instructionFetchQueue.isEmpty()) {
				Integer curMemAddress = instructionFetchQueue.poll();
				if (curMemAddress != null) {
					Instruction retrieved = computer.getInstruction(curMemAddress);
					if (retrieved != null) {
						try {
							retrieved.fetch(computer);
							instructionDecodeQueue.add(curMemAddress);
							this.fireOnFetch(retrieved);
						}
						catch (StopInstructionException ex) {
							
						}
						finally {
							instructionFetchQueue.add((int)computer.getIf_id_npc().getRegisterValue());
						}
					}
				}
			}
		} catch (StallException ex) {
			
		} finally {
			this.currentCycle++;
		}
	}
	
	public void addPipelineListener(PipelineListener listener) {
		this.listeners.add(listener);
	}
	
	public void removePipelineListener(PipelineListener listener) {
		this.listeners.remove(listener);
	}

	private void fireOnFetch(Instruction instruction) {
		for (PipelineListener listener : listeners) {
			listener.onFetch(instruction);
		}
	}
	
	private void fireOnDecode(Instruction instruction) {
		for (PipelineListener listener : listeners) {
			listener.onDecode(instruction);
		}
	}
	
	private void fireOnExecute(Instruction instruction) {
		for (PipelineListener listener : listeners) {
			listener.onExecute(instruction);
		}
	}
	
	private void fireOnMemoryAccess(Instruction instruction) {
		for (PipelineListener listener : listeners) {
			listener.onMemoryAccess(instruction);
		}
	}
	
	private void fireOnWriteBack(Instruction instruction) {
		for (PipelineListener listener : listeners) {
			listener.onWriteBack(instruction);
		}
	}
	
}
